package top.codedance.iotp.client.manage.net.handler.taskHandler;

import cd.io.netty.channel.ChannelHandlerContext;
import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.manage.ManageApplication;
import top.codedance.iotp.client.manage.net.listenner.TaskExecuter;
import top.codedance.iotp.common.constent.Command;
import top.codedance.iotp.common.entity.CommonEntity;
import top.codedance.iotp.common.util.SystemUtil;
import org.apache.log4j.Logger;

import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class DownloadExecuterImpl implements TaskExecuter {
    private static Logger logger = ManageApplication.getLogger(DownloadExecuterImpl.class);
    private static volatile Map<String, Long> tasksOverLength = new HashMap<>();
    private static volatile Map<String, Map<Integer, File>> tasksTempFile = new HashMap<>();
    private static volatile Map<String, Thread> tasksJoiner = new HashMap<>();

    private synchronized void initData(String taskId){
        if (!tasksOverLength.containsKey(taskId)) {
            tasksOverLength.put(taskId, 0l);
            tasksTempFile.put(taskId, new HashMap<>());
        }
    }

    private synchronized Long getTasksOverLength(String taskId) {
        return tasksOverLength.get(taskId);
    }

    private synchronized void accumulation(String taskId, int length){
        tasksOverLength.put(taskId, tasksOverLength.get(taskId) + length);
    }

    private synchronized void addTempFile(String taskId, Integer dataIndex, File file){
        Map<Integer, File> integerFileMap = tasksTempFile.get(taskId);
        integerFileMap.put(dataIndex, file);
        tasksTempFile.put(taskId, integerFileMap);
    }

    private synchronized boolean existsTempFile(String taskId, Integer dataIndex){
        if(tasksTempFile.containsKey(taskId)){
            return tasksTempFile.get(taskId).containsKey(dataIndex);
        }else{
            return false;
        }
    }

    private synchronized void addJoinner(String cTaskId, Thread t){
        tasksJoiner.put(cTaskId, t);
    }

    private synchronized void removeJoinner(String cTaskId){
        tasksJoiner.remove(cTaskId);
    }

    private synchronized boolean existsJoinner(String cTaskId){
        return tasksJoiner.containsKey(cTaskId);
    }

    @Override
    public void exec(ChannelHandlerContext channelHandlerContext, byte[] data, String taskId) {
        try {
            String jsonStr = new String(data, StandardCharsets.UTF_8);
            JSONObject jsonObject = JSONObject.parseObject(jsonStr);
            final String cTaskId = jsonObject.getString("taskId");
            final String version = jsonObject.getString("version");
            final Integer dataIndex = jsonObject.getInteger("dataIndex");
            final Long contentLength = jsonObject.getLong("contentLength");
            int readLength = jsonObject.getIntValue("bodyLength");
            byte[] body = DatatypeConverter.parseHexBinary(jsonObject.getString("body"));
            final String runtimeDirPath = SystemUtil.getRuntimeDirPath(ManageApplication.class);
            final String endClientFilePath = runtimeDirPath + "client-" + version + ".jar";
            String tempDirPath = runtimeDirPath + "upgarde/" + cTaskId + "/";
            final File tempDir = new File(tempDirPath);
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }
            synchronized (this.getClass()) {
                if (!tasksOverLength.containsKey(cTaskId)) {
                    initData(cTaskId);
                }
            }

            if(!existsTempFile(cTaskId, dataIndex)) {
                String downloadTempFragmentPath = tempDirPath + dataIndex;
                File tmpWriteFile = new File(downloadTempFragmentPath);
                tmpWriteFile.createNewFile();
                FileOutputStream fos = new FileOutputStream(tmpWriteFile);
                fos.write(body);
                fos.flush();
                fos.close();
                accumulation(cTaskId, readLength);
                addTempFile(cTaskId, dataIndex, tmpWriteFile);
                logger.debug("Download complete : " + Math.floor(getTasksOverLength(cTaskId) * 1.0 / contentLength * 100) + "%");
            }

            synchronized (this.getClass()) {
                if (!existsJoinner(cTaskId)) {
                    Thread t = new Thread(() -> {
                        while (true) {
                            if (getTasksOverLength(cTaskId) >= contentLength) {
                                try {
                                    logger.debug("Start join download file to: " + endClientFilePath);
                                    Map<Integer, File> taskTempMap = tasksTempFile.get(cTaskId);
                                    File file = new File(endClientFilePath);
                                    if (file.exists()) {
                                        file.delete();
                                    }
                                    FileChannel resultFileChannel = new FileOutputStream(file, true).getChannel();
                                    for (int i = 0; i < taskTempMap.size(); i++) {
                                        File file2 = taskTempMap.get(i);
                                        FileChannel blk = new FileInputStream(file2).getChannel();
                                        resultFileChannel.transferFrom(blk, resultFileChannel.size(), blk.size());
                                        blk.close();
                                    logger.debug("Join complete : " + Math.round(i * 1.0 / taskTempMap.size() * 100) + "%");
                                    }
                                    resultFileChannel.close();
                                    tasksOverLength.remove(cTaskId);
                                    tasksTempFile.remove(cTaskId);
                                    for (int i = 0; i < taskTempMap.size(); i++) {
                                        File file2 = taskTempMap.get(i);
                                        if (file2.exists()) {
                                            file2.delete();
                                        }
                                    }
                                    File parentFile = tempDir.getParentFile();
                                    tempDir.delete();
                                    parentFile.delete();
                                    try {
                                        ManageApplication.setVersion(Long.parseLong(version));
                                    } catch (Exception e) {
                                        logger.debug(e.getMessage());
                                    }
                                    ManageApplication.endUpgrade();
                                    Thread.sleep(1000);
                                    File preVersionClientFile = new File(runtimeDirPath + "client-" + ManageApplication.preClientVersionCode + ".jar");
                                    if (preVersionClientFile.exists()) {
                                        preVersionClientFile.delete();
                                    }
                                    removeJoinner(cTaskId);
                                    logger.debug("Upgarde complete.");
                                    break;
                                } catch (Exception e) {
                                    logger.debug("Upgarde fail.");
                                }
                            } else {
                                try {
                                    Thread.sleep(1000);
                                } catch (Exception e) {
                                }
                            }
                        }
                    });
                    synchronized (this.getClass()) {
                        if (!existsJoinner(cTaskId)) {
                            addJoinner(cTaskId, t);
                            t.start();
                        }
                    }
                }
            }
        }catch (Exception e){
            logger.debug("Download client part fail.");
            ManageApplication.endUpgrade();
        } finally {
            CommonEntity commonEntity2 = new CommonEntity();
            commonEntity2.setCommand(Command.DOWNLOAD_RECEIVE_ANSWER);
            channelHandlerContext.writeAndFlush(commonEntity2.build());
        }
    }
}
